package com.atguigu.gmall.list.service.impl;

import com.alibaba.fastjson.JSONObject;
import com.atguigu.gmall.list.dao.GoodsDao;
import com.atguigu.gmall.list.service.ListService;
import com.atguigu.gmall.model.list.*;
import com.atguigu.gmall.model.product.BaseCategoryView;
import com.atguigu.gmall.model.product.BaseTrademark;
import com.atguigu.gmall.model.product.SkuInfo;
import com.atguigu.gmall.product.client.ProductFeignClient;
import org.apache.lucene.search.join.ScoreMode;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.aggregations.Aggregation;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.bucket.nested.ParsedNested;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedLongTerms;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.elasticsearch.core.ElasticsearchRestTemplate;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import java.io.IOException;
import java.math.BigDecimal;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @author: 李旭
 * @create: 2021-07-13 15:43
 **/
@Service
public class ListServiceImpl implements ListService {

    @Autowired
    private ElasticsearchRestTemplate elasticsearchRestTemplate;//索引功能非常强大 性能非常好
    @Autowired
    private RestHighLevelClient restHighLevelClient;

    @Autowired  //Mybatis-Plus  SpringDataES
    private GoodsDao goodsDao;
    @Autowired
    private ProductFeignClient productFeignClient;
    @Autowired
    private RedisTemplate redisTemplate;

    //下架  删除索引
    @Override
    public void cancelSale(Long skuId) {
        goodsDao.deleteById(skuId);
    }

    //更新热度
    @Override
    public void hotScore(Long skuId) {
        //优化： 方案将热度先保存缓存中 保存到一定程度更新一次ES索引库 即可
        //Redis 缓存 五大数据类型 ：
        //String类型(值类型）    List类型（列表类型）  Hash类型（散列类型）
        // Set类型（集合类型  ZSet类型（有序集合）
        /**
         *   小明   语文 100 数学 88  英语 24
         *
         *   hotScore ： 1 热度分  2 100 3
         */

        //Double hotScore = redisTemplate.opsForZSet().score("hotScore", skuId);
        //redisTemplate.opsForZSet().add("hotScore",skuId,hotScore + 1); 98.34
        Double hotScore = redisTemplate.opsForZSet().incrementScore("hotScore", skuId, 1);
        if (hotScore % 10 == 0) {
            //ES : 优点：查询快  缺点：更新慢 保存慢 删除慢 超级慢
            //ES： 底层Lucene技术实现  保存 删除  更新=先删除+再保存
            Optional<Goods> byId = goodsDao.findById(skuId);
            Goods goods = byId.get();
            goods.setHotScore(Math.round(hotScore));//向上是ceil  向下是floor  round:四舍五入
            goodsDao.save(goods); //Goods中的主键是否已经存在了 存在了更新 不存在就是保存
            System.out.println("ES中分数更新了");
        }
        System.out.println("ES中分数不更新");

    }

    //开始搜索
    @Override
    public SearchResponseVo list(SearchParam searchParam) {
        //1:构建请求搜索对象
        SearchRequest searchRequest = buildSearchRequest(searchParam);
        //2:执行搜索
        SearchResponse searchResponse = null;
        try {
            searchResponse = restHighLevelClient.
                    search(searchRequest, RequestOptions.DEFAULT);
        } catch (IOException e) {
            e.printStackTrace();
        }
        //3:解析结果 searchResponse
        SearchResponseVo vo = parseSearchResponse(searchResponse);
   /*     "total": 1,
                "pageSize": null,
                "pageNo": null,
                "totalPages": null*/
       vo.setPageNo(searchParam.getPageNo());
       vo.setPageSize(searchParam.getPageSize());
       //    向上取整(total/pageSize) 判断余数
        //  18   3   6页
        long totalPages = (vo.getTotal() + vo.getPageSize() -1 )/vo.getPageSize();
        vo.setTotalPages(totalPages);
        return vo;
    }

    //解析结果
    private SearchResponseVo parseSearchResponse(SearchResponse searchResponse) {
        SearchResponseVo vo = new SearchResponseVo();

        SearchHits hits = searchResponse.getHits();
        //总条数
        long totalHits = hits.totalHits;
        vo.setTotal(totalHits);
        System.out.println("总条数:" + totalHits);
        //商品
        SearchHit[] hits1 = hits.getHits();
        if (null != hits1 && hits1.length > 0) {
            List<Goods> goodsList = Arrays.stream(hits1).map(hit -> {
                String sourceAsString = hit.getSourceAsString();
                Goods goods = JSONObject.parseObject(sourceAsString, Goods.class);

                Map<String, HighlightField> highlightFields = hit.getHighlightFields();
                if(highlightFields != null && highlightFields.size() >0){
                    HighlightField title = highlightFields.get("title");
                    String h = title.fragments()[0].toString();
                    goods.setTitle(h);
                }
                return goods;
            }).collect(Collectors.toList());
            vo.setGoodsList(goodsList);
        }
        //品牌集合
        Map<String, Aggregation> asMap = searchResponse.getAggregations().getAsMap();
        // K : aggTmId V : Aggregation
        ParsedLongTerms aggTmId = (ParsedLongTerms) asMap.get("aggTmId");
        List<SearchResponseTmVo> trademarkList = aggTmId.getBuckets().stream().map(b -> {
            SearchResponseTmVo tmVo = new SearchResponseTmVo();
            //ID
            tmVo.setTmId(b.getKeyAsNumber().longValue());
            //名称
            Terms aggTmName = ((Terms.Bucket) b).getAggregations().get("aggTmName");
            tmVo.setTmName(aggTmName.getBuckets().get(0).getKeyAsString());
            //Logo
            Terms aggTmLogoUrl = ((Terms.Bucket) b).getAggregations().get("aggTmLogoUrl");
            tmVo.setTmLogoUrl(aggTmLogoUrl.getBuckets().get(0).getKeyAsString());
            return tmVo;
        }).collect(Collectors.toList());
        vo.setTrademarkList(trademarkList);

        //平台属性集合
        ParsedNested aggAttrs = (ParsedNested) asMap.get("aggAttrs");
        Terms aggAttrId = aggAttrs.getAggregations().get("aggAttrId");
        List<SearchResponseAttrVo> attrsList = aggAttrId.getBuckets().stream().map(b -> {
            SearchResponseAttrVo attrVo = new SearchResponseAttrVo();
            //ID
            attrVo.setAttrId(b.getKeyAsNumber().longValue());
            //名称
            Terms aggAttrName = ((Terms.Bucket) b).getAggregations().get("aggAttrName");
            attrVo.setAttrName(aggAttrName.getBuckets().get(0).getKeyAsString());
            //值集合
            Terms aggAttrValue = ((Terms.Bucket) b).getAggregations().get("aggAttrValue");
            List<String> attrValueList = aggAttrValue.getBuckets().stream().map(bucket -> {
                return ((Terms.Bucket) bucket).getKeyAsString();
            }).collect(Collectors.toList());
            attrVo.setAttrValueList(attrValueList);
            return attrVo;
        }).collect(Collectors.toList());
        vo.setAttrsList(attrsList);
        return vo;
    }

    //构建请求搜索对象
    private SearchRequest buildSearchRequest(SearchParam searchParam) {
        //1:准备 SearchReqeust对象   GET goods/_search
        SearchRequest searchRequest = new SearchRequest();
        //2:创建搜索资源构建对象  相当于DSL
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        //组合条件对象
        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
        //3.1:查询
        String keyword = searchParam.getKeyword();
        if (!StringUtils.isEmpty(keyword)) {
            //有关键词
            //searchSourceBuilder.query( QueryBuilders.matchQuery("title",keyword));
            //must: AND
            //should:OR
            //mustNot : not
            boolQueryBuilder.must(QueryBuilders.matchQuery("title", keyword));
        } else {
            //查询所有
            //searchSourceBuilder.query(QueryBuilders.matchAllQuery());
            boolQueryBuilder.must(QueryBuilders.matchAllQuery());
        }
        //3.2 品牌    trademark : 品牌ID:品牌的名称
        String trademark = searchParam.getTrademark();
        if (!StringUtils.isEmpty(trademark)) {
            String[] t = StringUtils.split(trademark, ":");
            //searchSourceBuilder.query(QueryBuilders.termQuery("tmId",t[0]));
            boolQueryBuilder.filter(QueryBuilders.termQuery("tmId", t[0]));
        }
        //3.3 一二三级分类
        Long category1Id = searchParam.getCategory1Id();
        Long category2Id = searchParam.getCategory2Id();
        Long category3Id = searchParam.getCategory3Id();
        if (null != category1Id) {
            boolQueryBuilder.filter(QueryBuilders.termQuery("category1Id", category1Id));
        }
        if (null != category2Id) {
            boolQueryBuilder.filter(QueryBuilders.termQuery("category2Id", category2Id));
        }
        if (null != category3Id) {
            boolQueryBuilder.filter(QueryBuilders.termQuery("category3Id", category3Id));
        }
        //3.4 查询平台属性
        String[] props = searchParam.getProps();//Springmvc 接收参数 数组参数、
        if (null != props && props.length > 0) {
            //prop:  平台属性ID:平台属性值:平台属性名称
            BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
            for (String prop : props) {
                String[] p = prop.split(":");//3
                //"attrId" : 1,  p[0]
                //    "attrName" : "价格",p[2]
                //    "attrValue" : "4500-11999" p[1]
                BoolQueryBuilder subBoolQuery = QueryBuilders.boolQuery();
                subBoolQuery.must(QueryBuilders.termQuery("attrs.attrId", p[0]));
                subBoolQuery.must(QueryBuilders.termQuery("attrs.attrValue", p[1]));
                boolQuery.must(
                        QueryBuilders.nestedQuery("attrs", subBoolQuery, ScoreMode.None));
            }
            //过滤条件
            boolQueryBuilder.filter(boolQuery);
        }
        //将所有组合的条件放入{}
        searchSourceBuilder.query(boolQueryBuilder);
        //分页
        Integer pageNo = searchParam.getPageNo();
        Integer pageSize = searchParam.getPageSize();
        searchSourceBuilder.from((pageNo - 1) * pageSize);
        searchSourceBuilder.size(pageSize);
        ///高亮
        HighlightBuilder highlightBuilder = new HighlightBuilder();
        highlightBuilder
                 .field("title")
                 .preTags("<font color='red'>")
                 .postTags("</font>");

        searchSourceBuilder.highlighter(highlightBuilder);
        //排序  1:desc  1:asc  2:desc 2:asc ...
        String order = searchParam.getOrder();
        if(!StringUtils.isEmpty(order)){
            String[] o = StringUtils.split(order, ":");
            if(null != o && o.length == 2){
                String s = "";
                switch (o[0]){
                    case "1": s = "hotScore";break;
                    case "2": s = "price";break;
                }
                //排序
                searchSourceBuilder.sort(s,
                        o[1].equalsIgnoreCase("desc")?SortOrder.DESC:SortOrder.ASC);
            }
        }else{
            //默认 综合排序  降序
            searchSourceBuilder.sort("hotScore", SortOrder.DESC);
        }
        //桶聚合  group by    品牌ID（父） 品牌名称（子) 品牌Logo(子）
        //AggregationBuilders.terms() 别名
        searchSourceBuilder.aggregation(AggregationBuilders.terms("aggTmId").field("tmId").size(100)
                   .subAggregation(AggregationBuilders.terms("aggTmName").field("tmName"))
                   .subAggregation(AggregationBuilders.terms("aggTmLogoUrl").field("tmLogoUrl")));
        //聚合： 指标聚合 max avg ..  桶聚合 ： group by
        searchSourceBuilder.aggregation(AggregationBuilders.nested("aggAttrs","attrs")
                .subAggregation(AggregationBuilders.terms("aggAttrId").field("attrs.attrId")
                .subAggregation(AggregationBuilders.terms("aggAttrName").field("attrs.attrName"))
                .subAggregation(AggregationBuilders.terms("aggAttrValue").field("attrs.attrValue")))
        );

        System.out.println(searchSourceBuilder.toString());//DSL语句
        //4:将搜索资源构建对象 {} 设置请求搜索对象
        searchRequest.source(searchSourceBuilder);
        //设置索引库的名称  indices
        String[] indices = {"goods"};
        searchRequest.indices(indices);
        //设置类型type
        //searchRequest.types("info","hoaa");
        return searchRequest;
    }

    //上架  保存索引
    @Override
    public void onSale(Long skuId) {
        Goods goods = new Goods();

        //1:根据skuId 查询skuInfo
        SkuInfo skuInfo = productFeignClient.getSkuInfo(skuId);
        goods.setId(skuInfo.getId());
        goods.setDefaultImg(skuInfo.getSkuDefaultImg());
        goods.setTitle(skuInfo.getSkuName());
        //实时价格
        BigDecimal price = productFeignClient.getPrice(skuId);
        goods.setPrice(price.doubleValue());
        //时间  保存ES的时间
        Calendar instance = Calendar.getInstance();
        //instance.setTimeZone(TimeZone.getDefault());
        //instance.add
        Date time = instance.getTime();
        goods.setCreateTime(time);
//////////////////////////////////////////////////////////////////
        //2:根据tmId 查询 base_trademark
        BaseTrademark baseTrademark = productFeignClient.getBaseTrademark(skuInfo.getTmId());
        goods.setTmId(baseTrademark.getId());
        goods.setTmName(baseTrademark.getTmName());
        goods.setTmLogoUrl(baseTrademark.getLogoUrl());
//////////////////////////////////////////////////////////////////
        //3:根据category3_id 查询 分类视图
        BaseCategoryView baseCagegoryView = productFeignClient.getBaseCagegoryView(skuInfo.getCategory3Id());
        goods.setCategory1Id(baseCagegoryView.getCategory1Id());
        goods.setCategory1Name(baseCagegoryView.getCategory1Name());
        goods.setCategory2Id(baseCagegoryView.getCategory2Id());
        goods.setCategory2Name(baseCagegoryView.getCategory2Name());
        goods.setCategory3Id(baseCagegoryView.getCategory3Id());
        goods.setCategory3Name(baseCagegoryView.getCategory3Name());
        //////////////////////////////////////////////////////////////////
        //4:根据skuId 查询base_attr_info（平台属性名） base_attr_value（平台属性值表）  sku_attr_value (关联表）
        List<Map> skuAttrValueListMap = productFeignClient.getSkuAttrValue(skuId);
        //平台属性集合
        List<SearchAttr> attrs = skuAttrValueListMap.stream().map(map -> {
            SearchAttr searchAttr = new SearchAttr();
            //平台属性的ID   Object  --> String --> Long
            searchAttr.setAttrId(Long.parseLong(String.valueOf(map.get("id"))));
            //平台属性的名字
            searchAttr.setAttrName(String.valueOf(map.get("aname")));
            //平台属性值的名字
            searchAttr.setAttrValue(String.valueOf(map.get("vname")));
            return searchAttr;
        }).collect(Collectors.toList());
        goods.setAttrs(attrs);
        //保存
        goodsDao.save(goods);
    }

}
